跳到主要内容

Inventory 与主机分组

Ansible 干活之前得先知道「有哪些机器、它们是什么角色」,这份花名册就是 Inventory。这篇笔记记录静态 Inventory 的 INI 与 YAML 两种写法、主机分组与变量目录约定、patterns 目标选择器的常用套路,最后简单带过动态 Inventory。示例控制节点为 macOS / Linux,受管节点为 Ubuntu 22.04,所有命令都可以直接照抄执行。

Inventory 是什么​

Inventory(主机清单)是 Ansible 管理对象的定义文件,里面写着:

  • 有哪些主机:主机名或 IP 地址,以及连接它们用的参数(端口、用户、私钥等)。
  • 主机属于哪些组:按角色(web、db)或环境(prod、staging)分组,命令和 playbook 都以组为操作单位。
  • 主机和组的变量:比如 nginx 监听端口、时区、环境名,写在 inventory 或旁边的 group_vars/、host_vars/ 目录里。

Ansible 默认读取 /etc/ansible/hosts,实际项目里建议把 inventory 放在项目目录内,用 -i 参数或 ansible.cfg 指定:

ansible all -i inventory/hosts.ini -m ansible.builtin.ping
# ansible.cfg
[defaults]
inventory = inventory/hosts.ini
项目内自管理 inventory

把 inventory、ansible.cfg、group_vars/、host_vars/ 一起放进 git 仓库,换一台控制节点 clone 下来就能用,不依赖控制节点上的全局配置。

静态 Inventory 写法​

静态 Inventory 支持 INI 和 YAML 两种格式,功能等价,按团队习惯选一种即可。

INI 格式​

INI 格式紧凑直观,是最常见的写法:

# inventory/hosts.ini
# 未分组的主机直接写在文件顶部,属于隐式的 all 组
bastion.example.com

[web]
web1.example.com
web2.example.com
web3.example.com

[db]
db1.example.com

YAML 格式​

YAML 格式结构清晰,变量多的时候比 INI 好维护:

# inventory/hosts.yml
all:
hosts:
bastion.example.com:
children:
web:
hosts:
web1.example.com:
web2.example.com:
web3.example.com:
db:
hosts:
db1.example.com:
两种格式选定一种

同一个项目里建议只选一种格式。INI 写起来快,YAML 在嵌套组和变量多时更不容易出错。Ansible 根据文件内容自动识别格式,不需要额外声明。

主机范围​

连续编号的主机用范围写法,不用一台台列。web[1:3].example.com 展开为 web1、web2、web3:

[web]
web[1:3].example.com

[k8s]
node[01:10].internal.example.com # 前导零保留:node01 ~ node10

范围也支持字母:db-[a:f].example.com 展开为 db-a 到 db-f。

连接变量​

主机名解析不到、SSH 端口不是 22、登录用户不是当前用户时,用连接变量告诉 Ansible 怎么连:

[web]
web1 ansible_host=192.168.1.11 ansible_port=22 ansible_user=ubuntu
web2 ansible_host=192.168.1.12 ansible_port=2222 ansible_user=ops ansible_ssh_private_key_file=~/.ssh/ops_ed25519

常用连接变量:

变量说明
ansible_host实际连接的地址(IP 或域名),与清单里的主机名解耦
ansible_portSSH 端口,默认 22
ansible_userSSH 登录用户
ansible_ssh_private_key_file登录用的私钥路径
ansible_connection连接插件,默认 ssh,本机可设 local
ansible_python_interpreter受管节点的 Python 解释器路径,自动发现失败时手动指定

YAML 格式下的写法:

all:
children:
web:
hosts:
web1:
ansible_host: 192.168.1.11
ansible_port: 22
ansible_user: ubuntu

分组与嵌套​

组是 Ansible 的组织单元。除了直接分组,还可以用 [父组:children] 把多个组拼成一个大组:

[web]
web1.example.com
web2.example.com

[db]
db1.example.com

[prod:children]
web
db

这样 prod 组就包含 web 和 db 的全部主机,一条命令可以打整个生产环境。

组变量​

[组名:vars] 给整组主机设置变量,组内每台主机都能读到:

[prod:children]
web
db

[prod:vars]
env=production
ntp_server=ntp.aliyun.com

INI 的 vars 块里一行一个 键=值,只适合简单值;列表、字典这类复杂结构建议交给 YAML 格式的 inventory 或下一节的 group_vars/ 目录。

all 组

所有主机都属于隐式的 all 组,[all:vars] 或 group_vars/all.yml 里的变量对所有主机生效。主机同时属于多个组时,它会拿到每个组的变量,同名变量的取舍见「变量优先级」一节。

变量目录约定​

变量一多,全塞进 inventory 文件里会很难看。Ansible 约定在 inventory 文件(或 playbook)旁边建两个目录:

inventory/
├── hosts.ini
├── group_vars/
│ ├── all.yml # 所有主机生效
│ ├── web.yml # web 组生效
│ └── prod.yml # prod 组生效
└── host_vars/
└── web1.example.com.yml # 只对这一台主机生效

规则很简单:

  • group_vars/ 下的文件按组名命名,如 web.yml 对应 [web] 组。
  • host_vars/ 下的文件按主机名命名,如 web1.example.com.yml 对应清单里的 web1.example.com。
  • 文件内容是纯 YAML 字典,不用包外层结构:
# group_vars/web.yml
nginx_port: 8080
nginx_worker_processes: auto
  • 组名 / 主机名对应的文件也可以换成同名目录,目录下的所有 .yml 文件都会被加载,适合把一个大组的变量拆成多个文件管理。

变量优先级​

同一个变量在多处定义时,优先级高的覆盖低的。日常最常用的几层从低到高:

优先级来源典型位置
1(最低)role defaultsroles/xxx/defaults/main.yml
2group_vars/allgroup_vars/all.yml
3组变量group_vars/web.yml、[web:vars]
4主机变量host_vars/web1.example.com.yml
5play varsplaybook 里的 vars:
6(最高)extra vars命令行 -e "key=value"
优先级是简化版

完整优先级链有 20 多级(host facts、vars_files、task vars 等都插在中间),上面只列最常用的六层。记住「越靠近具体对象、越靠近命令行,优先级越高」这条规律,基本不会判断错。

临时覆盖变量测试时,-e 最方便:

ansible web -i inventory/hosts.ini -m ansible.builtin.debug -a "var=nginx_port" -e "nginx_port=9090"

Patterns 目标选择​

ansible <pattern> 的第一个参数就是 pattern,决定命令落到哪些主机上。常用写法:

写法含义示例
all全部主机ansible all -m ansible.builtin.ping
web某个组或某台主机ansible web -m ansible.builtin.ping
*通配符ansible 'web*' -m ansible.builtin.ping
:并集ansible 'web:db' -m ansible.builtin.ping
:!排除ansible 'web:!web3.example.com' -m ansible.builtin.ping
:&交集ansible 'web:&prod' -m ansible.builtin.ping
~正则匹配ansible '~web[12]\.example\.com' -m ansible.builtin.ping
web[0]组内下标ansible 'web[0]' -m ansible.builtin.ping

几个能直接跑的组合示例:

# 只打 prod 里的 web(交集)
ansible 'web:&prod' -i inventory/hosts.ini -m ansible.builtin.ping

# web 组里跳过 web3(排除)
ansible 'web:!web3.example.com' -i inventory/hosts.ini -m ansible.builtin.ping

# 只对 web 组的第一台做灰度发布
ansible 'web[0]' -i inventory/hosts.ini -m ansible.builtin.command -a "uptime"

# web 和 db 一起(并集)
ansible 'web:db' -i inventory/hosts.ini -m ansible.builtin.command -a "uptime"
pattern 一定要加引号

*、~、! 都是 shell 的特殊字符,不加引号会被 shell 先展开或报错。养成习惯:pattern 一律用单引号包起来。下标写法也支持切片,web[0:1] 取前两台。

用 ansible-inventory 验证​

inventory 写复杂之后,先别急着跑任务,用 ansible-inventory 确认解析结果:

# 树状图看分组结构
ansible-inventory -i inventory/hosts.ini --graph
@all:
|--@db:
| |--db1.example.com
|--@prod:
| |--@db:
| | |--db1.example.com
| |--@web:
| | |--web1.example.com
| | |--web2.example.com
| | |--web3.example.com
|--@ungrouped:
| |--bastion.example.com
|--@web:
| |--web1.example.com
| |--web2.example.com
| |--web3.example.com
# 完整 JSON(含合并后的变量),接 jq 过滤
ansible-inventory -i inventory/hosts.ini --list | jq '.web.hosts'

# 只看某台主机最终拿到哪些变量
ansible-inventory -i inventory/hosts.ini --host web1.example.com
["web1.example.com", "web2.example.com", "web3.example.com"]

--host 输出的是合并了 all、组变量、host_vars 之后的最终变量表,排查「变量怎么不是预期值」时第一步就看它。

动态 Inventory 简介​

静态文件适合机器数量稳定的环境。云主机弹性伸缩、Auto Scaling 组里的机器随时增删时,手工维护清单不现实,这时用动态 Inventory 插件:执行时实时调云平台 API,按标签(tag)、地域等自动生成主机清单和分组。

以 AWS 为例,写一个文件名以 aws_ec2.yml 结尾的清单文件:

# inventory/aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
- ap-southeast-1
keyed_groups:
# 按 EC2 标签 Role 自动分组,得到 role_web、role_db 等组
- key: tags.Role
prefix: role

使用上和静态文件没有区别,-i inventory/aws_ec2.yml 即可(需要安装 amazon.aws collection 和 boto3)。Azure、GCP、阿里云等都有对应插件,用 ansible-doc -t inventory -l 查看当前可用的插件列表。入门阶段先把静态 inventory 用熟,动态插件只是数据来源不同,分组、变量、patterns 的玩法完全一样。

常见坑​

  • 组名用连字符:web-prod 这样的组名在清单里能用,但 Jinja 模板里 groups.web-prod 点号写法会被当成减法报错,必须写成 groups['web-prod']。建议组名统一用下划线,省得记两套写法。同理组名大小写敏感,Web 和 web 是两个组。
  • 变量同名被意外覆盖:主机同时属于多个组时,同名变量只有一份生效(通常子组覆盖父组、同层按组名字母序后者生效)。靠「巧合」维系的覆盖关系,加一个新组就可能翻车,重要变量尽量写到 host_vars/ 或显式分层。
  • inventory 路径相对谁解析:ansible.cfg 里的 inventory = 是相对 ansible.cfg 所在目录解析的;而 host_vars/、group_vars/ 是跟在 inventory 文件旁边(或 playbook 旁边)才生效。目录放错位置时 Ansible 不报错,只是变量静默不生效,用 ansible-inventory --host 一查便知。
  • 范围写法的位数陷阱:web[1:10] 得到 web1~web10(web1 无零填充),而 DNS 里注册的是 web01 的话就连不上。需要补零就写 web[01:10],两边对齐再收工。

小结​

  • Inventory 是主机花名册:静态写法有 INI 和 YAML 两种,功能等价,选定一种用到底。
  • 分组解决「按角色批量操作」,[父组:children] 解决「按环境整体操作」,连接变量解决「怎么连上这台机器」。
  • 变量按 group_vars/、host_vars/ 目录约定存放,优先级记住「越具体、越靠命令行越高」,-e 是临时覆盖的利器。
  • 动手前用 ansible-inventory --graph 看结构、--host 看变量,把「以为」变成「确认」。
  • 机器频繁变动再上动态 Inventory 插件,玩法与静态清单一致。